package creation

import (
	"context"
	"fmt"
	"github.com/nuttech/bell"
	"github.com/stretchr/testify/mock"
	v1alpha12 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/kubeapi"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset/mocks"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/rest/fake"
	"testing"
	"time"
)

func TestWatcherController(t *testing.T) {
	logs.Logger = zap.NewNop()

	kubeapi.GetClient = func() *kubernetes.Clientset {
		return kubernetes.New(&fake.RESTClient{})
	}

	ti := v1alpha12.TriggerInstance{
		Status: v1alpha12.TriggerInstanceStatus{
			ResourceStatus: []v1alpha12.ResourceStatus{
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "REMOVED",
					Phase:      "",
					Status:     "REMOVED",
				},
			},
		},
	}
	ti2 := v1alpha12.TriggerInstance{
		ObjectMeta: v1.ObjectMeta{
			Name:      "test",
			Namespace: "test",
			Labels: map[string]string{
				"test": "test",
			},
			Annotations: map[string]string{
				"test": "test",
			},
		},
		Status: v1alpha12.TriggerInstanceStatus{
			ResourceStatus: []v1alpha12.ResourceStatus{
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "REMOVED",
					Phase:      "",
					Status:     "REMOVED",
				},
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "",
					Phase:      "",
					Status:     "",
				},
			},
		},
	}

	templateClient := mocks.NewTemplateInterface(t)
	triggerClient := mocks.NewTriggerInterface(t)
	scopeClient := mocks.NewScopeInterface(t)
	triggerInstanceClient := mocks.NewTriggerInstanceInterface(t)
	triggerInstanceClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ti, nil).Maybe()
	triggerInstanceClient.On("UpdateStatus", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe()

	clt := clientset.V1Alpha1Client{
		TriggerClient:         triggerClient,
		TemplateClient:        templateClient,
		ScopeClient:           scopeClient,
		TriggerInstanceClient: triggerInstanceClient,
	}

	operator.Cl = &clt
	kubeapi.GetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *kubeapi.ResourceMeta {
		return &kubeapi.ResourceMeta{
			Name:         "Pod",
			Plural:       "pods",
			Path:         "api",
			Namespaced:   true,
			GroupVersion: "v1",
		}
	}

	w := WatcherController{
		ReSyncPeriod: 0,
		watchObjects: map[string]v1alpha12.TriggerInstance{},
	}

	t.Run("Start", func(t *testing.T) {
		w.Start()
	})

	t.Run("UpdateSyncPeriod", func(t *testing.T) {
		w.UpdateSyncPeriod(bell.Message{
			Event:     "sync",
			Timestamp: time.Time{},
			Value:     uint64(5),
		})
	})

	t.Run("addNewObject", func(t *testing.T) {
		w.AddNewObject(bell.Message{
			Event:     "add",
			Timestamp: time.Time{},
			Value:     ti,
		})
	})

	podJson := "{\"kind\":\"Pod\",\"apiVersion\":\"v1\",\"metadata\":{\"generateName\":\"keymaker-keymaker-769579fd65-\",\"creationTimestamp\":null,\"labels\":{\"app.kubernetes.io/instance\":\"keymaker\",\"app.kubernetes.io/managed-by\":\"spinnaker\",\"app.kubernetes.io/name\":\"keymaker\",\"pod-template-hash\":\"769579fd65\"},\"annotations\":{\"artifact.spinnaker.io/location\":\"keymaker\",\"artifact.spinnaker.io/name\":\"keymaker-keymaker\",\"artifact.spinnaker.io/type\":\"kubernetes/deployment\",\"moniker.spinnaker.io/application\":\"securitykaaskeymaker\",\"moniker.spinnaker.io/cluster\":\"deployment keymaker-keymaker\",\"keymaker.k8s-integration.sfdc.com/inject\":\"true\",\"madkub.k8s-integration.sfdc.com/inject\":\"true\",\"port/value\":\"1111\"},\"ownerReferences\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"ReplicaSet\",\"name\":\"keymaker-keymaker-769579fd65\",\"uid\":\"dcdda283-a4fc-11e9-a276-42010a3e0104\",\"controller\":true,\"blockOwnerDeletion\":true}]},\"spec\":{\"volumes\":[{\"name\":\"kaas-keymaker-orlgsgcsl34imsg6-token-kn2t8\",\"secret\":{\"secretName\":\"kaas-keymaker-orlgsgcsl34imsg6-token-kn2t8\"}}],\"initContainers\":[{\"name\":\"ca-populator-original\",\"image\":\"/dva/pki_aws_bootstrap:6\",\"args\":[\"/bin/cp\",\"/etc/ssl/certs/cacerts.pem\",\"/etc/pki_service/ca/cacerts.pem\"],\"resources\":{},\"terminationMessagePath\":\"/dev/termination-log\",\"terminationMessagePolicy\":\"File\",\"imagePullPolicy\":\"IfNotPresent\"},{\"name\":\"madkub-init-original\",\"image\":\"/dva/pki_aws_bootstrap:6\",\"args\":[\"/bin/cp\",\"/etc/ssl/certs/cacerts.pem\",\"/etc/pki_service/ca/cacerts.pem\"],\"resources\":{},\"terminationMessagePath\":\"/dev/termination-log\",\"terminationMessagePolicy\":\"File\",\"imagePullPolicy\":\"IfNotPresent\"}],\"containers\":[{\"name\":\"keymaker-original\",\"image\":\"gcr.io/gsf-mgmt-devmvp-spinnaker/dva/keymaker-client:latest\",\"command\":[\"sleep\"],\"args\":[\"infinity\"],\"ports\":[{\"name\":\"http\",\"containerPort\":7020,\"protocol\":\"TCP\"}],\"resources\":{},\"volumeMounts\":[{\"name\":\"kaas-keymaker-orlgsgcsl34imsg6-token-kn2t8\",\"readOnly\":true,\"mountPath\":\"/var/run/secrets/kubernetes.io/serviceaccount\"}],\"terminationMessagePath\":\"/dev/termination-log\",\"terminationMessagePolicy\":\"File\",\"imagePullPolicy\":\"Always\"}],\"restartPolicy\":\"Always\",\"terminationGracePeriodSeconds\":30,\"dnsPolicy\":\"ClusterFirst\",\"serviceAccountName\":\"kaas-keymaker-orlgsgcsl34imsg6\",\"serviceAccount\":\"kaas-keymaker-orlgsgcsl34imsg6\",\"automountServiceAccountToken\":true,\"securityContext\":{},\"schedulerName\":\"default-scheduler\",\"tolerations\":[{\"key\":\"node.kubernetes.io/not-ready\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300},{\"key\":\"node.kubernetes.io/unreachable\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300}],\"priority\":0},\"status\":{}}"

	t.Run("UpdateStatus all removed", func(t *testing.T) {
		w.UpdateStatus()
	})

	triggerInstanceClient = mocks.NewTriggerInstanceInterface(t)
	triggerInstanceClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ti2, nil).Maybe()
	triggerInstanceClient.On("UpdateStatus", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe()

	clt = clientset.V1Alpha1Client{
		TriggerClient:         triggerClient,
		TemplateClient:        templateClient,
		ScopeClient:           scopeClient,
		TriggerInstanceClient: triggerInstanceClient,
	}

	operator.Cl = &clt

	t.Run("RemoveObject", func(t *testing.T) {
		w.RemoveObject(bell.Message{
			Event:     "add",
			Timestamp: time.Time{},
			Value:     ti,
		})
	})

	kubeapi.GetRawResourceFromKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *kubeapi.ResourceMeta, resourceName string, namespaceName string) ([]byte, error) {
		return []byte(podJson), nil
	}

	t.Run("UpdateStatus", func(t *testing.T) {
		w.AddNewObject(bell.Message{
			Event:     "add",
			Timestamp: time.Time{},
			Value:     ti2,
		})
		w.UpdateStatus()
	})
}

func TestCheckTiNotFound(t *testing.T) {
	logs.Logger = zap.NewNop()

	kubeapi.GetClient = func() *kubernetes.Clientset {
		return kubernetes.New(&fake.RESTClient{})
	}

	ti := v1alpha12.TriggerInstance{
		Status: v1alpha12.TriggerInstanceStatus{
			ResourceStatus: []v1alpha12.ResourceStatus{
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "REMOVED",
					Phase:      "",
					Status:     "REMOVED",
				},
			},
		},
	}

	templateClient := mocks.NewTemplateInterface(t)
	triggerClient := mocks.NewTriggerInterface(t)
	scopeClient := mocks.NewScopeInterface(t)
	triggerInstanceClient := mocks.NewTriggerInstanceInterface(t)
	triggerInstanceClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, "not found")).Maybe()
	triggerInstanceClient.On("UpdateStatus", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe()

	clt := clientset.V1Alpha1Client{
		TriggerClient:         triggerClient,
		TemplateClient:        templateClient,
		ScopeClient:           scopeClient,
		TriggerInstanceClient: triggerInstanceClient,
	}

	operator.Cl = &clt
	kubeapi.GetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *kubeapi.ResourceMeta {
		return &kubeapi.ResourceMeta{
			Name:         "Pod",
			Plural:       "pods",
			Path:         "api",
			Namespaced:   true,
			GroupVersion: "v1",
		}
	}

	w := WatcherController{
		ReSyncPeriod: 0,
		watchObjects: map[string]v1alpha12.TriggerInstance{
			ti.GetNamespacedName(): ti,
		},
	}

	t.Run("UpdateStatus ti not found", func(t *testing.T) {
		w.UpdateStatus()
	})
}

func TestCheckResourceNotFound(t *testing.T) {
	logs.Logger = zap.NewNop()

	kubeapi.GetClient = func() *kubernetes.Clientset {
		return kubernetes.New(&fake.RESTClient{})
	}

	ti := v1alpha12.TriggerInstance{
		Status: v1alpha12.TriggerInstanceStatus{
			ResourceStatus: []v1alpha12.ResourceStatus{
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "Normal",
					Phase:      "",
					Status:     "Normal",
				},
			},
		},
	}

	templateClient := mocks.NewTemplateInterface(t)
	triggerClient := mocks.NewTriggerInterface(t)
	scopeClient := mocks.NewScopeInterface(t)
	triggerInstanceClient := mocks.NewTriggerInstanceInterface(t)
	triggerInstanceClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ti, nil).Maybe()
	triggerInstanceClient.On("UpdateStatus", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe()

	clt := clientset.V1Alpha1Client{
		TriggerClient:         triggerClient,
		TemplateClient:        templateClient,
		ScopeClient:           scopeClient,
		TriggerInstanceClient: triggerInstanceClient,
	}

	operator.Cl = &clt
	kubeapi.GetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *kubeapi.ResourceMeta {
		return &kubeapi.ResourceMeta{
			Name:         "Pod",
			Plural:       "pods",
			Path:         "api",
			Namespaced:   true,
			GroupVersion: "v1",
		}
	}

	w := WatcherController{
		ReSyncPeriod: 0,
		watchObjects: map[string]v1alpha12.TriggerInstance{
			ti.GetNamespacedName(): ti,
		},
	}

	kubeapi.GetRawResourceFromKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *kubeapi.ResourceMeta, resourceName string, namespaceName string) ([]byte, error) {
		return nil, errors.NewNotFound(schema.GroupResource{}, "not found")
	}

	t.Run("UpdateStatus resource not found", func(t *testing.T) {
		w.UpdateStatus()
	})
}

func TestCheckResourceError(t *testing.T) {
	logs.Logger = zap.NewNop()

	kubeapi.GetClient = func() *kubernetes.Clientset {
		return kubernetes.New(&fake.RESTClient{})
	}

	ti := v1alpha12.TriggerInstance{
		Status: v1alpha12.TriggerInstanceStatus{
			ResourceStatus: []v1alpha12.ResourceStatus{
				{
					Name:       "pod",
					Kind:       "Pod",
					ApiVersion: "v1",
					Message:    "Normal",
					Phase:      "",
					Status:     "Normal",
				},
			},
		},
	}

	templateClient := mocks.NewTemplateInterface(t)
	triggerClient := mocks.NewTriggerInterface(t)
	scopeClient := mocks.NewScopeInterface(t)
	triggerInstanceClient := mocks.NewTriggerInstanceInterface(t)
	triggerInstanceClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ti, nil).Maybe()
	triggerInstanceClient.On("UpdateStatus", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe()

	clt := clientset.V1Alpha1Client{
		TriggerClient:         triggerClient,
		TemplateClient:        templateClient,
		ScopeClient:           scopeClient,
		TriggerInstanceClient: triggerInstanceClient,
	}

	operator.Cl = &clt
	kubeapi.GetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *kubeapi.ResourceMeta {
		return &kubeapi.ResourceMeta{
			Name:         "Pod",
			Plural:       "pods",
			Path:         "api",
			Namespaced:   true,
			GroupVersion: "v1",
		}
	}

	w := WatcherController{
		ReSyncPeriod: 0,
		watchObjects: map[string]v1alpha12.TriggerInstance{
			ti.GetNamespacedName(): ti,
		},
	}

	kubeapi.GetRawResourceFromKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *kubeapi.ResourceMeta, resourceName string, namespaceName string) ([]byte, error) {
		return nil, fmt.Errorf("Resource error")
	}

	t.Run("UpdateStatus resource not found", func(t *testing.T) {
		w.UpdateStatus()
	})
}
